package com.bilibili.brouter.apt.internal

import com.bilibili.brouter.api.BootStrapMode
import com.bilibili.brouter.api.DEFAULT
import com.bilibili.brouter.api.Launcher
import com.bilibili.brouter.api.StandardRouteType
import com.bilibili.brouter.api.internal.module.DefaultModuleActivator
import com.bilibili.brouter.api.task.ThreadMode
import com.bilibili.brouter.apt.*
import com.bilibili.brouter.common.analysis.Analyzer
import com.bilibili.brouter.common.compile.GENERATED_PACKAGE
import com.bilibili.brouter.common.compile.META_PATH
import com.bilibili.brouter.common.compile.gson
import com.bilibili.brouter.common.meta.*
import okio.ByteString.Companion.encodeUtf8
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.SourceVersion
import javax.tools.StandardLocation

class MetaCollectorImpl : MetaCollector {

    private val modules = mutableMapOf<String, ModuleMeta>()
    private val metas =
        mutableMapOf<String, Triple<MutableList<RouteMeta>, MutableList<ServiceMeta>, MutableList<TaskMeta>>>()
    private var defaultModuleName: String? = null

    // consumer
    private val consumers = mutableMapOf<String, ServiceConsumerClass>()

    private fun moduleOf(moduleName: String): Triple<MutableList<RouteMeta>, MutableList<ServiceMeta>, MutableList<TaskMeta>> {
        return metas.getOrPut(moduleName) {
            Triple(mutableListOf(), mutableListOf(), mutableListOf())
        }
    }

    fun done(env: ProcessingEnvironment) {
        metas.remove("")?.let { anonymous ->
            // select the default, and add to it
            val name = defaultModuleName
                ?: when (modules.size) {
                    0 -> {
                        // At least one route/service/task, because we have anonymous module.
                        // Generate module name by class name in our library, it is distinct.
                        val name = "b" + (
                                when {
                                    anonymous.first.isNotEmpty() -> anonymous.first[0].className
                                    anonymous.second.isNotEmpty() -> anonymous.second[0].sourceClassName
                                    else -> anonymous.third[0].className
                                }
                                ).encodeUtf8().sha1().hex()
                        addModule {
                            it.moduleName(name)
                                .desc("Generated by BRouter.")
                        }
                        name
                    }
                    1 -> {
                        modules.keys.single()
                    }
                    else -> error("Found anonymous routes or services, can't decide which module them belongs to: ${modules.keys}.")
                }
            metas[name]!!.let { target ->
                target.first += anonymous.first
                target.second += anonymous.second
                target.third += anonymous.third
            }
        }
        metas.keys.forEach {
            if (!modules.containsKey(it)) {
                error("Module named '$it' is not exists that some routes and services declared.")
            }
        }
        modules.values.forEach { m ->
            // every service dependsOn onCreate
            // onPostCreate dependsOn onCreate
            m.onCreate?.taskName?.let { onCreate ->
                m.services.forEach {
                    it.taskDependencies += onCreate
                }
                m.onPostCreate?.let { post ->
                    post.taskDependencies += onCreate
                }
            }
        }

        val library = LibraryMeta(modules.values.toList(), consumers.values.toList())

        Analyzer(listOf(library), true).analyze()

        modules.values.forEach {
            it.generateSource(env.filer)
        }

        consumers.values.forEach {
            it.generateSource(env.filer)
        }

        env.filer.createResource(
            StandardLocation.CLASS_OUTPUT,
            "",
            META_PATH
        ).openWriter().use {
            it.write(gson.toJson(library))
        }
    }

    override fun addModule(configure: (ModuleMetaBuilder) -> Unit) {
        val module = DefaultModuleMetaBuilder()
            .let {
                configure(it)
                it.build()
            }
        modules.put(
            module.moduleName, module
        )?.let {
            error("Found duplicated module name '${module.moduleName}'")
        }
    }

    override fun addRoute(configure: (RouteMetaBuilder) -> Unit) {
        val builder = DefaultRouteMetaBuilder()
        configure(builder)
        moduleOf(builder.moduleName).first += builder.build()
    }

    override fun addService(configure: (ServiceMetaBuilder) -> Unit) {
        val builder = DefaultServiceMetaBuilder()
        configure(builder)
        moduleOf(builder.moduleName).second += builder.build()
    }

    override fun addTask(configure: (TaskMetaBuilder) -> Unit) {
        val builder = DefaultTaskMetaBuilder()
        configure(builder)
        moduleOf(builder.moduleName).third += builder.build()
    }

    override fun addConsumer(configure: (ConsumerClassBuilder) -> Unit) {
        val consumer = DefaultConsumerClassBuilder().let {
            configure(it)
            it.build()
        }
        consumers[consumer.consumerClassName] = consumer
    }

    internal inner class DefaultModuleMetaBuilder : ModuleMetaBuilder {
        private var moduleName: String? = null
        private var defaultInLibrary: Boolean = true
        private var activatorClass: String = DefaultModuleActivator::class.java.name
        private var bootStrapMode: BootStrapMode = BootStrapMode.ON_INIT
        private var desc: String = ""
        private var attributes = emptyList<AttributeMeta>()
        private var onCreate: TaskMeta? = null
        private var onPostCreate: TaskMeta? = null

        override fun moduleName(moduleName: String): ModuleMetaBuilder = this.apply {
            this.moduleName = moduleName
        }

        override fun defaultInLibrary(defaultInLibrary: Boolean): ModuleMetaBuilder = this.apply {
            this.defaultInLibrary = defaultInLibrary
        }

        override fun activatorClass(activatorClass: String): ModuleMetaBuilder = this.apply {
            this.activatorClass = activatorClass.requireNonNullOrEmpty()
        }

        override fun bootStrapMode(bootStrapMode: BootStrapMode): ModuleMetaBuilder = this.apply {
            this.bootStrapMode = bootStrapMode
        }

        override fun desc(desc: String): ModuleMetaBuilder = this.apply {
            this.desc = desc
        }

        override fun attributes(attributes: List<Pair<String, String>>): ModuleMetaBuilder =
            this.apply {
                this.attributes = attributes.map {
                    AttributeMeta(it.first, it.second)
                }
            }

        override fun onCreate(configure: (TaskMetaBuilder) -> Unit): ModuleMetaBuilder =
            this.apply {
                onCreate = DefaultTaskMetaBuilder().let {
                    configure(it)
                    it.build()
                }
            }

        override fun onPostCreate(configure: (TaskMetaBuilder) -> Unit): ModuleMetaBuilder =
            this.apply {
                onPostCreate = DefaultTaskMetaBuilder().let {
                    configure(it)
                    it.build()
                }
            }

        fun build(): ModuleMeta {
            val name = requireNotNull(moduleName) {
                "Module name not specified."
            }
            require(name.isNotEmpty() && SourceVersion.isName(name)) {
                "Illegal module name: $name"
            }
            require(name != "BuiltInModules") {
                "Module name can't be 'BuiltInModules'"
            }

            if (defaultInLibrary) {
                defaultModuleName?.let {
                    error("Found two modules require default in library: $it, $name")
                }
                defaultModuleName = name
            }

            val (routes, services, tasks) = moduleOf(name)

            return ModuleMeta(
                name,
                bootStrapMode.name,
                desc,
                "$GENERATED_PACKAGE.${name.split('_', '-').joinToString("") { it.capitalize() }}",
                activatorClass,
                onCreate,
                onPostCreate,
                attributes,
                routes,
                services,
                tasks
            )
        }
    }

    internal class DefaultServiceMetaBuilder : ServiceMetaBuilder {
        private var serviceName: String = DEFAULT
        var moduleName: String = ""
        private var returnType: String? = null
        private var sourceClassName: String? = null
        private var sourceMethodName: String? = null
        private var serviceTypes: List<String> = emptyList()
        private var singleton: Boolean = false
        private var desc: String = ""
        private val methodParams = mutableListOf<ServiceDependency>()
        private var taskDependencies = emptyList<String>()


        override fun serviceName(serviceName: String): ServiceMetaBuilder = this.apply {
            this.serviceName = serviceName
        }

        override fun moduleName(moduleName: String): ServiceMetaBuilder = this.apply {
            this.moduleName = moduleName
        }

        override fun returnType(returnType: String): ServiceMetaBuilder = this.apply {
            this.returnType = returnType
        }

        override fun sourceClassName(sourceClassName: String): ServiceMetaBuilder = this.apply {
            this.sourceClassName = sourceClassName
        }

        override fun sourceMethodName(sourceMethodName: String): ServiceMetaBuilder = this.apply {
            this.sourceMethodName = sourceMethodName
        }

        override fun serviceTypes(serviceTypes: List<String>): ServiceMetaBuilder = this.apply {
            this.serviceTypes = serviceTypes.toList()
        }

        override fun singleton(singleton: Boolean): ServiceMetaBuilder = this.apply {
            this.singleton = singleton
        }

        override fun desc(desc: String): ServiceMetaBuilder = this.apply {
            this.desc = desc
        }

        override fun addMethodParam(
            className: String,
            serviceName: String,
            type: DependencyType,
            optional: Boolean
        ): ServiceMetaBuilder = this.apply {
            methodParams += ServiceDependency(
                className.requireNonNullOrEmpty(),
                serviceName,
                DepType.valueOf(type.name),
                optional
            )
        }

        override fun taskDependencies(taskDependencies: List<String>): ServiceMetaBuilder =
            this.apply {
                this.taskDependencies = taskDependencies
            }

        fun build(): ServiceMeta {
            val returnType = returnType.requireNonNullOrEmpty()
            val sourceClassName = sourceClassName.requireNonNullOrEmpty()
            val sourceMethodName = sourceMethodName.requireNonNullOrEmpty()
            val serviceTypes = this.serviceTypes.let {
                if (it.isEmpty()) listOf(returnType)
                else serviceTypes
            }
            return ServiceMeta(
                serviceName,
                returnType,
                sourceClassName,
                sourceMethodName,
                serviceTypes,
                singleton,
                desc,
                methodParams.toList(),
                taskDependencies.toList()
            )
        }
    }

    internal class DefaultRouteMetaBuilder : RouteMetaBuilder {
        private var routeName: String? = null
        var moduleName: String = ""
        private var routeRules: List<String> = emptyList()
        private var routeType: String = StandardRouteType.NATIVE
        private var attributes = emptyList<Pair<String, String>>()
        private var interceptors = emptyList<String>()
        private var launcher = Launcher::class.java.name
        private var desc = ""
        private var className: String? = null
        private var exported: Boolean = false

        override fun routeName(routeName: String): RouteMetaBuilder = this.apply {
            this.routeName = routeName
        }

        override fun moduleName(moduleName: String): RouteMetaBuilder = this.apply {
            this.moduleName = moduleName
        }

        override fun routeRules(routeRules: List<String>): RouteMetaBuilder = this.apply {
            this.routeRules = routeRules.toList()
        }

        override fun routeType(routeType: String): RouteMetaBuilder = this.apply {
            this.routeType = routeType
        }

        override fun attributes(attributes: List<Pair<String, String>>): RouteMetaBuilder =
            this.apply {
                this.attributes = attributes.toList()
            }

        override fun interceptors(interceptors: List<String>): RouteMetaBuilder = this.apply {
            this.interceptors = interceptors.toList()
        }

        override fun launcher(launcher: String): RouteMetaBuilder = this.apply {
            this.launcher = launcher
        }

        override fun desc(desc: String): RouteMetaBuilder = this.apply {
            this.desc = desc
        }

        override fun className(className: String): RouteMetaBuilder = this.apply {
            this.className = className
        }

        override fun exported(exported: Boolean): RouteMetaBuilder = this.apply {
            this.exported = exported
        }

        fun build(): RouteMeta {
            val routeRules = routeRules.also {
                require(it.isNotEmpty()) {
                    "No route rule."
                }
            }
            // choose the first if no name
            val routeName = routeName ?: routeRules[0]
            return RouteMeta(
                routeName,
                routeRules,
                routeType,
                attributes.map {
                    AttributeMeta(it.first, it.second)
                },
                interceptors,
                launcher,
                desc,
                className.requireNonNullOrEmpty(),
                exported
            )
        }
    }

    internal class DefaultTaskMetaBuilder : TaskMetaBuilder {
        private var taskName: String? = null
        var moduleName: String = ""
        private var priority: Int = 0
        private var threadMode = ThreadMode.ANY
        private var className: String? = null
        private var taskDependencies = emptyList<String>()
        private val constructorParams = mutableListOf<ServiceDependency>()
        private val producedServices = mutableListOf<TaskOutputMeta>()

        override fun taskName(taskName: String): TaskMetaBuilder = this.apply {
            this.taskName = taskName
        }

        override fun moduleName(moduleName: String): TaskMetaBuilder = this.apply {
            this.moduleName = moduleName
        }

        override fun priority(priority: Int): TaskMetaBuilder = this.apply {
            this.priority = priority
        }

        override fun threadMode(threadMode: ThreadMode): TaskMetaBuilder = this.apply {
            this.threadMode = threadMode
        }

        override fun className(className: String): TaskMetaBuilder = this.apply {
            this.className = className
        }

        override fun addConstructorParam(
            className: String,
            serviceName: String,
            type: DependencyType,
            optional: Boolean
        ): TaskMetaBuilder = this.apply {
            this.constructorParams += ServiceDependency(
                className.requireNonNullOrEmpty(),
                serviceName,
                DepType.valueOf(type.name),
                optional
            )
        }

        override fun taskDependencies(taskDependencies: List<String>): TaskMetaBuilder =
            this.apply {
                this.taskDependencies = taskDependencies.toList()
            }

        override fun addProducedService(
            serviceName: String,
            returnType: String,
            serviceTypes: List<String>,
            desc: String,
            fieldOrMethodName: String,
            isField: Boolean
        ): TaskMetaBuilder = this.apply {
            producedServices += TaskOutputMeta(
                serviceName,
                returnType.requireNonNullOrEmpty(),
                serviceTypes,
                desc,
                fieldOrMethodName,
                isField
            )
        }

        fun build(): TaskMeta {
            return TaskMeta(
                taskName.requireNonNullOrEmpty().also {
                    require(!it.contains('.')) {
                        "Task name shouldn't contains dot: '$it'."
                    }
                },
                priority,
                threadMode.name,
                className.requireNonNullOrEmpty(),
                constructorParams.toList(),
                taskDependencies,
                producedServices.toList()
            )
        }
    }

    internal class DefaultConsumerClassBuilder : ConsumerClassBuilder {
        private var consumerClassName: String? = null
        private var superConsumerClassName: String? = null
        private val consumerDetails = mutableListOf<ServiceConsumer>()

        override fun consumerClassName(consumerClassName: String): ConsumerClassBuilder =
            this.apply {
                this.consumerClassName = consumerClassName
            }

        override fun superConsumerClassName(superConsumerClassName: String): ConsumerClassBuilder =
            this.apply {
                this.superConsumerClassName = superConsumerClassName
            }

        override fun addConsumerDetail(
            className: String,
            serviceName: String,
            type: DependencyType,
            optional: Boolean,
            fieldOrMethodName: String,
            isField: Boolean
        ): ConsumerClassBuilder = this.apply {
            consumerDetails += ServiceConsumer(
                fieldOrMethodName,
                isField,
                ServiceDependency(
                    className.requireNonNullOrEmpty(),
                    serviceName,
                    DepType.valueOf(type.name),
                    optional
                )
            )
        }


        fun build(): ServiceConsumerClass {
            return ServiceConsumerClass(
                consumerClassName.requireNonNullOrEmpty(),
                superConsumerClassName,
                consumerDetails.toList()
            )
        }
    }
}

internal fun String?.requireNonNullOrEmpty(): String {
    if (this == null || this.isEmpty()) {
        error("Illegal class name $this")
    }
    return this
}

